//-------------------------------------------------------------------
//	Copyright (c) 2010-2014  Zhirnov Andrey
//	This file is part of the "UX Standard Lib" project.
//	See copyright notice in "UX_STL_Core.h".
//-------------------------------------------------------------------

#ifndef _UXS_FILE_ADDRESS_H
#define _UXS_FILE_ADDRESS_H

#pragma once

#include "../Containers/TString.h"
#include "../Containers/TUniArray.h"
#include "../Containers/TArray.h"
#include "../Math/Mathematics.h"

namespace UX_STL
{
namespace UXTypes
{

	//
	// File Address Utils
	//

	struct FileAddressUtils
	{
	// methods
	public:
		static uni_c_string		GetPath (uni_c_string filename);
		static uni_c_string		GetName (uni_c_string filename);
		static uni_c_string		GetExtension (uni_c_string filename);
		static uni_c_string		GetNameAndExt (uni_c_string filename);

		static void		DividePath (uni_c_string filename, OUT array<uni_c_string> &path);

		static void		FormatPath (INOUT string &filename);

		static void		RemoveName (INOUT string &filename);
		static void		RemoveExtension (INOUT string &filename);
		static bool		PathMoveBack (INOUT string &path);

		static bool		RemoveDirectoriesFromLeft (INOUT string &path, uint numDirectories);
		static bool		RemoveDirectoriesFromRight (INOUT string &path, uint numDirectories);

		static void		AddDirectoryToPath (INOUT string &path, uni_c_string dir);
		static void		AddBaseDirectoryToPath (INOUT string &path, uni_c_string dir);
		static void		AddNameToPath (INOUT string &path, uni_c_string name);
		static void		AddExtensionToName (INOUT string &filename, uni_c_string ext);

		static string &	BuildPath (OUT string &result, uni_c_string path, uni_c_string nameWithExt);
		static string &	BuildPath (OUT string &result, uni_c_string path, uni_c_string name, uni_c_string ext);

	private:
		static usize	_GetExtensionPos (uni_c_string filename);
		static usize	_GetNamePos (uni_c_string filename);
		static usize	_SafeInc (usize value, uni_c_string str);
	};



	//
	// File Address Builder
	//

	struct FileAddress
	{
	// types
	public:
		typedef FileAddress		Self;


	// variables
	private:
		string			_prefix;
		string			_name;
		mutable string	_path;
		mutable string	_full;
		mutable bool	_changed;

		
	// methods
	public:
		FileAddress () : _changed(true)
		{}

		FileAddress (uni_c_string filename) : _changed(true)
		{
			_path = FileAddressUtils::GetPath( filename );
			_name = FileAddressUtils::GetNameAndExt( filename );
		}

		Self & SetPrefix (uni_c_string prefix)
		{
			_changed = true;
			_prefix = prefix;
			return *this;
		}

		Self & SetPath (uni_c_string path)
		{
			_changed = true;
			_path = path;
			return *this;
		}
		
		Self & AddPath (uni_c_string dir)
		{
			_changed = true;
			FileAddressUtils::AddDirectoryToPath( _path, dir ); 
			return *this;
		}

		Self & AddBasePath (uni_c_string dir)
		{
			_changed = true;
			FileAddressUtils::AddBaseDirectoryToPath( _path, dir );
			return *this;
		}

		bool MoveBackDir ()
		{
			_changed = true;
			return FileAddressUtils::PathMoveBack( _path );
		}

		Self & SetName (uni_c_string nameWithExt)
		{
			_changed = true;
			_name = nameWithExt;
			return *this;
		}

		Self & AddExtension (uni_c_string ext)
		{
			_changed = true;

			if ( not _name.Empty() and _name.Back() != '.' )
				_name << '.';

			_name << ext;
			return *this;
		}

		Self & ChangeExtension (uni_c_string ext)
		{
			_changed = true;
			FileAddressUtils::RemoveExtension( _name );
			_name << '.' << ext;
			return *this;
		}

		Self & operator / (uni_c_string dir)
		{
			return AddPath( dir );
		}

		bool operator == (const Self &right) const
		{
			return Full() == right.Full();
		}

		bool operator != (const Self &right) const
		{
			return not( (*this) == right );
		}

		bool operator > (const Self &right) const
		{
			return Full() < right.Full();
		}

		bool operator >= (const Self &right) const
		{
			return Full() >= right.Full();
		}

		bool operator < (const Self &right) const
		{
			return right > (*this);
		}
	
		bool operator <= (const Self &right) const
		{
			return not ( (*this) > right );
		}

		uni_c_string	Full () const
		{
			if ( _changed )
			{
				_changed = false;

				FileAddressUtils::FormatPath( _path );

				_full.ClearMem();
				_full << _prefix << _path;
				FileAddressUtils::AddNameToPath( _full, _name );
			}
			return _full;
		}

		uni_c_string	Path ()	const			{ return _path; }
		uni_c_string	Name () const			{ return FileAddressUtils::GetName( _name ); }
		uni_c_string	Ext () const			{ return FileAddressUtils::GetExtension( _name ); }
		uni_c_string	NameWithExt () const	{ return _name; }
	};

	
	
/*
=================================================
	_GetExtensionPos
=================================================
*/
	inline usize  FileAddressUtils::_GetExtensionPos (uni_c_string filename)
	{
		for (usize i = filename.Length()-1; i < filename.Length(); --i)
		{
			if ( filename[i] == '.' )
				return i;
		}
		return filename.Length();
	}
	
/*
=================================================
	_GetNamePos
=================================================
*/
	inline usize  FileAddressUtils::_GetNamePos (uni_c_string filename)
	{
		for (usize i = filename.Length()-1; i < filename.Length(); --i)
		{
			const char c = filename[i];

			if ( c == '/' or c == '\\' )
				return i;
		}
		return 0;
	}
	
/*
=================================================
	_SafeInc
=================================================
*/
	inline usize  FileAddressUtils::_SafeInc (usize value, uni_c_string str)
	{
		return	value == 0 ? 0 :
				(value+1 < str.Length() ? value+1 : str.Length());
	}

/*
=================================================
	GetPath
=================================================
*/
	inline uni_c_string  FileAddressUtils::GetPath (uni_c_string filename)
	{
		return uni_c_string( filename.ptr(), _GetNamePos( filename ) );
	}
	
/*
=================================================
	GetName
=================================================
*/
	inline uni_c_string  FileAddressUtils::GetName (uni_c_string filename)
	{
		const isize	name_pos = _SafeInc( _GetNamePos( filename ), filename );
		const isize ext_pos  = _GetExtensionPos( filename );
		return uni_c_string( filename.ptr() + name_pos, UXMath::Max( ext_pos - name_pos, 0 ) );
	}
	
/*
=================================================
	GetExtension
=================================================
*/
	inline uni_c_string  FileAddressUtils::GetExtension (uni_c_string filename)
	{
		const usize ext_pos = _SafeInc( _GetExtensionPos( filename ), filename );
		return uni_c_string( filename.ptr() + ext_pos, filename.Length() - ext_pos );
	}
	
/*
=================================================
	GetNameAndExt
=================================================
*/
	inline uni_c_string  FileAddressUtils::GetNameAndExt (uni_c_string filename)
	{
		const usize name_pos = _SafeInc( _GetNamePos( filename ), filename );
		return uni_c_string( filename.ptr() + name_pos, filename.Length() - name_pos );
	}
	
/*
=================================================
	DividePath
=================================================
*/
	inline void  FileAddressUtils::DividePath (uni_c_string filename, OUT array<uni_c_string> &path)
	{
		usize	prev_pos = 0;

		path.ClearMem();
		path.Reserve(64);

		for (usize i = 0; i < filename.Length(); ++i)
		{
			const char c = filename[i];

			if ( c == '/' or c == '\\' )
			{
				const isize len = i - prev_pos;

				if ( len > 0 )
					path.PushBack( uni_c_string( filename.ptr() + prev_pos, len ) );

				prev_pos = i+1;
			}
		}

		const isize len = filename.Length() - prev_pos;

		if ( len > 0 )
			path.PushBack( uni_c_string( filename.ptr() + prev_pos, len ) );
	}
	
/*
=================================================
	FormatPath
=================================================
*/
	inline void  FileAddressUtils::FormatPath (INOUT string &filename)
	{
		if ( filename.Empty() )
			return;

		static_array<UXMath::svec2, 128>	pos_size_arr;
		usize								arr_len		= 0;
		usize								last_pos	= 0;
		string								temp;

		for (usize i = 0; i < filename.Length(); ++i)
		{
			const char c = filename[i];

			if ( c == '/' or c == '\\' )
			{
				pos_size_arr[arr_len++] = UXMath::svec2( last_pos, i-last_pos );
				last_pos = i+1;
			}
		}

		temp = filename.cstr() + last_pos;

		uint	back = 0;

		for (isize i = arr_len-1; i >= 0; --i)
		{
			if ( pos_size_arr[i][1] == 2 and
				 filename[ pos_size_arr[i][0]   ] == '.' and
				 filename[ pos_size_arr[i][0]+1 ] == '.' )
			{
				 ++back;
			}
			else
			{
				if ( back != 0 ) {
					--back;
					continue;
				}
				else
				{
					'/' >> temp;
					temp.Insert( uni_c_string( filename.cstr() + pos_size_arr[i][0], pos_size_arr[i][1] ), 0 );
				}
			}
		}

		for (usize i = 0; i < back; ++i) {
			"../" >> temp;
		}

		filename.SetString( temp );
	}
	
/*
=================================================
	RemoveName
=================================================
*/
	inline void  FileAddressUtils::RemoveName (INOUT string &filename)
	{
		const usize name_pos = _GetNamePos( filename );
		filename.Erase( name_pos, filename.Length() - name_pos );
	}
	
/*
=================================================
	RemoveExtension
=================================================
*/
	inline void  FileAddressUtils::RemoveExtension (INOUT string &filename)
	{
		const usize ext_pos = _GetExtensionPos( filename );
		filename.Erase( ext_pos, filename.Length() - ext_pos );
	}
	
/*
=================================================
	PathMoveBack
=================================================
*/
	inline bool  FileAddressUtils::PathMoveBack (INOUT string &path)
	{
		for (usize i = path.Length()-1; i < path.Length(); --i)
		{
			const char c = path[i];

			if ( c == '/' or c == '\\' )
			{
				path.SetLength( i );
				return true;
			}
		}
		return false;
	}
	
/*
=================================================
	RemoveDirectoriesFromLeft
=================================================
*/
	inline bool  FileAddressUtils::RemoveDirectoriesFromLeft (INOUT string &path, const uint numDirectories)
	{
		if ( numDirectories == 0 )
			return true;

		int dirs = numDirectories;
		
		usize i = 0;

		for (; i < path.Length(); ++i)
		{
			const char c = path[i];

			if ( c == '/' or c == '\\' )
			{
				if ( --dirs <= 0 )
					break;
			}
		}
		
		path.Erase( i, path.Length() - i );

		return (dirs <= 0);
	}
	
/*
=================================================
	RemoveDirectoriesFromRight
=================================================
*/
	inline bool  FileAddressUtils::RemoveDirectoriesFromRight (INOUT string &path, const uint numDirectories)
	{
		if ( numDirectories == 0 )
			return true;

		int dirs = numDirectories;

		for (usize i = path.Length()-1; i < path.Length(); --i)
		{
			const char c = path[i];

			if ( c == '/' or c == '\\' )
			{
				path.SetLength( i );

				if ( --dirs <= 0 )
					return true;
			}
		}
		return false;
	}

/*
=================================================
	AddDirectoryToPath
=================================================
*/
	inline void  FileAddressUtils::AddDirectoryToPath (INOUT string &path, uni_c_string dir)
	{
		if ( dir.Empty() )
			return;

		if ( path.Empty() )
		{
			path = dir;
			return;
		}

		const char p = path.Back();
		const char d = dir.Front();

		if ( p != '/' and p != '\\' )
			path << '/';

		if ( d == '/' or d == '\\' )
			path.PopBack();
		
		path << dir;
	}
	
/*
=================================================
	AddBaseDirectoryToPath
=================================================
*/
	inline void  FileAddressUtils::AddBaseDirectoryToPath (INOUT string &path, uni_c_string dir)
	{
		if ( dir.Empty() )
			return;

		if ( path.Empty() )
		{
			path = dir;
			return;
		}

		const char p = path.Front();
		const char d = dir.Back();

		if ( p != '/' and p != '\\' )
			path.Insert( '/', 0 );

		if ( d == '/' or d == '\\' )
			path.Erase(0);
		
		path.Insert( dir, 0 );
	}

/*
=================================================
	AddNameToPath
=================================================
*/
	inline void  FileAddressUtils::AddNameToPath (INOUT string &path, uni_c_string name)
	{
		return AddDirectoryToPath( path, name );
	}
	
/*
=================================================
	AddExtensionToName
=================================================
*/
	inline void  FileAddressUtils::AddExtensionToName (INOUT string &filename, uni_c_string ext)
	{
		if ( ext.Empty() )
			return;

		if ( filename.Empty() or filename.Back() != '.' )
			filename << '.';

		if ( ext.Front() == '.' )
			filename.PopBack();

		filename << ext;
	}

/*
=================================================
	BuildPath
=================================================
*/
	inline string &  FileAddressUtils::BuildPath (OUT string &result, uni_c_string path, uni_c_string nameWithExt)
	{
		result = path;
		AddNameToPath( result, nameWithExt );
		return result;
	}

/*
=================================================
	BuildPath
=================================================
*/
	inline string &  FileAddressUtils::BuildPath (OUT string &result, uni_c_string path, uni_c_string name, uni_c_string ext)
	{
		result = path;
		AddNameToPath( result, name );
		AddExtensionToName( result, ext );
		return result;
	}


}	// UXTypes
}	// UX_STL

#endif	// _UXS_FILE_ADDRESS_H